
#include <utest.h>

#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <chrono>

#include <hello/algorithm/uhash.h>


std::vector<std::string> gen_similar_strings(size_t cnt)
{
    std::vector<std::string> _ans;
    _ans.reserve(cnt);
    for (auto i = 0; i < cnt; ++i)
    {
        _ans.push_back("hello this is string, and it's number is " + std::to_string(i) + ", i hope this will be helpful to you all.");
    }
    return std::move(_ans);
}

std::vector<std::string> gen_unique_strings(size_t cnt)
{
    srand(time(nullptr));
    std::vector<std::string> _ans;
    _ans.reserve(cnt);
    for (auto i = 0; i < cnt; ++i)
    {
        size_t len = rand() % (100 - 30 + 1) + 30;
        std::string tmp(len, '\0');
        for (auto j = 0; j < len; ++j)
        {
            tmp[j] = (char)(rand() % (110 - 10 + 1) + 10);
        }
        _ans.push_back(std::move(tmp));
    }
    return std::move(_ans);
}

void benchmark_test_func(const std::vector<std::string>& strs, const char* _name, const std::function<uint64_t(const std::string&)>& hasher)
{
    std::chrono::high_resolution_clock::time_point st, ed;
    st = std::chrono::high_resolution_clock::now();
    for (auto& str : strs)
    {
        hello::hasher<std::string>::hash(str);
    }
    ed = std::chrono::high_resolution_clock::now();
    println(_name, "cost:", std::chrono::duration_cast<std::chrono::milliseconds>(ed - st).count());
}

void distributed_test_func(const std::vector<std::string>& strs, const char* _name, size_t bkt_cnt, const std::function<uint64_t(const std::string&)>& hasher)
{
    auto str_cnt = strs.size();
    std::vector<int> buckets(bkt_cnt, 0);
    double _E = str_cnt / bkt_cnt;
    for (auto& str : strs)
    {
        buckets[(hasher(str.c_str()) % bkt_cnt)]++;
    }
    double _ans = 0.0;
    for (auto i = 0; i < bkt_cnt; ++i)
    {
        double _tmp = ((double)buckets[i] - _E) * ((double)buckets[i] - _E) / _E;
        _ans += _tmp;
    }
    println(_name, "Distribution:", _ans);
}


std::hash<std::string> std_hash;


TEST(benchmark, 50000_similar, true)
{
    std::vector<std::string> strs = gen_similar_strings(50000);

    benchmark_test_func(strs, "  U", hello::hasher<std::string>::hash);
    benchmark_test_func(strs, "Std", std_hash);
}

TEST(benchmark, 50000_unique, true)
{
    std::vector<std::string> strs = gen_unique_strings(50000);

    benchmark_test_func(strs, "  U", hello::hasher<std::string>::hash);
    benchmark_test_func(strs, "Std", std_hash);
}

TEST(benchmark, 500000_similar, true)
{
    std::vector<std::string> strs = gen_similar_strings(500000);
    
    benchmark_test_func(strs, "  U", hello::hasher<std::string>::hash);
    benchmark_test_func(strs, "Std", std_hash);
}

TEST(benchmark, 500000_unique, true)
{
    std::vector<std::string> strs = gen_unique_strings(500000);
    
    benchmark_test_func(strs, "  U", hello::hasher<std::string>::hash);
    benchmark_test_func(strs, "Std", std_hash);
}

TEST(distributed, 5000_similar, true)
{
    std::vector<std::string> strs = gen_similar_strings(5000);

    distributed_test_func(strs, "  U", 500, hello::hasher<std::string>::hash);
    distributed_test_func(strs, "Std", 500, std_hash);
}

TEST(distributed, 5000_unique, true)
{
    std::vector<std::string> strs = gen_unique_strings(5000);

    distributed_test_func(strs, "  U", 500, hello::hasher<std::string>::hash);
    distributed_test_func(strs, "Std", 500, std_hash);
}

TEST(distributed, 20000_similar, true)
{
    std::vector<std::string> strs = gen_similar_strings(20000);

    distributed_test_func(strs, "  U", 1000, hello::hasher<std::string>::hash);
    distributed_test_func(strs, "Std", 1000, std_hash);
}

TEST(distributed, 20000_unique, true)
{
    std::vector<std::string> strs = gen_unique_strings(20000);

    distributed_test_func(strs, "  U", 1000, hello::hasher<std::string>::hash);
    distributed_test_func(strs, "Std", 1000, std_hash);
}
